package com.magenta.maxoptra.gis.utils;

import java.util.HashMap;
import java.util.Map;

/**
 * User: KuznetsovD
 * Date: 28.08.2009
 * Time: 12:14:27
 */
public class UnitsConverter {

    private static final Map<Units, Integer> lineUnits = new HashMap<Units, Integer>();
    private static final Map<Units, Integer> speedUnits = new HashMap<Units, Integer>();
    private static final Map<Units, Integer> timeUnits = new HashMap<Units, Integer>();
    private static final Map<Units, Integer> weightUnits = new HashMap<Units, Integer>();

    static {
        lineUnits.put(LineUnits.MILLIMETER, 0);
        lineUnits.put(LineUnits.CENTIMETER, 1);
        lineUnits.put(LineUnits.DECIMETRE, 2);
        lineUnits.put(LineUnits.METER, 3);
        lineUnits.put(LineUnits.KILOMETRE, 4);
        lineUnits.put(LineUnits.FOOT, 5);
        lineUnits.put(LineUnits.INCH, 6);
        lineUnits.put(LineUnits.NAUTICAL_MILE, 7);
        lineUnits.put(LineUnits.MILE, 8);
        lineUnits.put(LineUnits.YARD, 9);

        speedUnits.put(SpeedUnits.METERS_PER_SEC, 0);
        speedUnits.put(SpeedUnits.KILOMETERS_PER_HOUR, 1);
        speedUnits.put(SpeedUnits.MILES_PER_HOUR, 2);

        timeUnits.put(TimeUnits.MILLI, 0);
        timeUnits.put(TimeUnits.SECOND, 1);
        timeUnits.put(TimeUnits.MINUTE, 2);
        timeUnits.put(TimeUnits.HOUR, 3);
        timeUnits.put(TimeUnits.DAY, 4);

        weightUnits.put(WeightUnits.KILOGRAM, 0);
        weightUnits.put(WeightUnits.GRAM, 1);
        weightUnits.put(WeightUnits.POUND_UK, 2);
        weightUnits.put(WeightUnits.TONNE, 3);
        weightUnits.put(WeightUnits.TON_UK, 4);
        weightUnits.put(WeightUnits.TON_US, 5);
    }

    public static final double[][] unitsLineMatrix = {
//            millimeter
            {1/*millimeter*/, 0.1/*centimeter*/, 0.01/*decimetre*/, 0.001/*meter*/, 0.000001/*kilometre*/, 0.003281/*foot*/, 0.03937/*inch*/, 5.400e-7/*nautical_mile*/, 6.214e-7/*mile*/, 0.001094/*yard*/},
//            centimeter
            {10/*millimeter*/, 1/*centimeter*/, 0.1/*decimetre*/, 0.01/*meter*/, 0.00001/*kilometre*/, 0.03281/*foot*/, 0.3937/*inch*/, 0.0000054/*nautical_mile*/, 0.000006214/*mile*/, 0.01094/*yard*/},
//            decimetre
            {100/*millimeter*/, 10/*centimeter*/, 1/*decimetre*/, 0.1/*meter*/, 0.0001/*kilometre*/, 0.3281/*foot*/, 3.937/*inch*/, 0.000054/*nautical_mile*/, 0.00006214/*mile*/, 0.1094/*yard*/},
//            meter
            {1000/*millimeter*/, 100/*centimeter*/, 10/*decimetre*/, 1/*meter*/, 0.001/*kilometre*/, 3.281/*foot*/, 39.37/*inch*/, 0.00054/*nautical_mile*/, 0.0006214/*mile*/, 1.094/*yard*/},
//            kilometre
            {1000000/*millimeter*/, 100000/*centimeter*/, 10000/*decimetre*/, 1000/*meter*/, 1/*kilometre*/, 3280.84/*foot*/, 39370.079/*inch*/, 0.54/*nautical_mile*/, 1 / 1.609/*mile*/, 1093.613/*yard*/},
//            foot
            {304.8/*millimeter*/, 30.48/*centimeter*/, 3.048/*decimetre*/, 0.3048/*meter*/, 0.0003048/*kilometre*/, 1/*foot*/, 12/*inch*/, 0.0001646/*nautical_mile*/, 0.0001894/*mile*/, 0.3333/*yard*/},
//            inch
            {25.4/*millimeter*/, 2.54/*centimeter*/, 0.254/*decimetre*/, 0.0254/*meter*/, 0.0000254/*kilometre*/, 0.08333/*foot*/, 1/*inch*/, 0.00001371/*nautical_mile*/, 0.00001578/*mile*/, 0.02778/*yard*/},
//            nautical_mile
            {1852000/*millimeter*/, 185200/*centimeter*/, 18520/*decimetre*/, 1852/*meter*/, 1.852/*kilometre*/, 6076.115/*foot*/, 72913.386/*inch*/, 1/*nautical_mile*/, 1.151/*mile*/, 2025.372/*yard*/},
//            mile
            {1609344/*millimeter*/, 160934.4/*centimeter*/, 16093.44/*decimetre*/, 1609.344/*meter*/, 1.609/*kilometre*/, 5280/*foot*/, 63360/*inch*/, 0.869/*nautical_mile*/, 1/*mile*/, 1760/*yard*/},
//            yard
            {914.4/*millimeter*/, 91.44/*centimeter*/, 9.144/*decimetre*/, 0.9144/*meter*/, 0.0009144/*kilometre*/, 3/*foot*/, 36/*inch*/, 0.0004937/*nautical_mile*/, 0.0005682/*mile*/, 1/*yard*/}
    };

    public static final double[][] unitsSpeedMatrix = {
            /*METERS_IN_SEC*/
            {1/*METERS_IN_SEC*/, 3.6/*KILOMETERS_IN_HOUR*/, 2.237/*MILES_IN_HOUR*/},

            /*KILOMETERS_IN_HOUR*/
            {0.2778/*METERS_IN_SEC*/, 1/*KILOMETERS_IN_HOUR*/, 0.6215/*MILES_IN_HOUR*/},

            /*MILES_IN_HOUR*/
            {0.447/*METERS_IN_SEC*/, 1.609/*KILOMETERS_IN_HOUR*/, 1/*MILES_IN_HOUR*/}
    };

    public static final double[][] unitsTimeMatrix = {
            /*MILLI*/
            {1/*MILLI*/, (double) 1 / 1000/*SEC*/, (double) 1 / 1000 / 60/*MIN*/, (double) 1 / 1000 / 60 / 60/*HOUR*/, (double) 1 / 1000 / 60 / 60 / 24/*DAY*/},

            /*SEC*/
            {1000/*MILLI*/, 1 /*SEC*/, (double) 1 / 60/*MIN*/, (double) 1 / 60 / 60/*HOUR*/, (double) 1 / 60 / 60 / 24/*DAY*/},

            /*MIN*/
            {1000 * 60/*MILLI*/, 60 /*SEC*/, 1/*MIN*/, (double) 1 / 60/*HOUR*/, (double) 1 / 60 / 24/*DAY*/},

            /*HOUR*/
            {1000 * 60 * 60/*MILLI*/, 60 * 60 /*SEC*/, 60/*MIN*/, 1 /*HOUR*/, (double) 1 / 24/*DAY*/},

            /*DAY*/
            {1000 * 60 * 60 * 24 /*MILLI*/, 60 * 60 * 24 /*SEC*/, 60 * 24 /*MIN*/, 24 /*HOUR*/, 1 /*DAY*/}
    };

    public static final double[][] unitsWeightMatrix = {
            /*KILOGRAM*/
            {1/*KILOGRAM*/, 1000/*GRAM*/, 2.204623/*POUND_UK*/, 0.001/*TONN*/, 0.000984/*UK_TONN*/, 0.001102/*US_TONN*/},
            /*GRAM*/
            {0.001/*KILOGRAM*/, 1/*GRAM*/, 0.002205/*POUND_UK*/, 0.000001/*TONN*/, 0.000001/*UK_TONN*/, 0.000001/*US_TONN*/},
            /*POUND_UK*/
            {0.453592/*KILOGRAM*/, 453.59237/*GRAM*/, 1/*POUND_UK*/, 0.000454/*TONN*/, 0.000446/*UK_TONN*/, 0.0005/*US_TONN*/},
            /*TONN*/
            {1000/*KILOGRAM*/, 1000000/*GRAM*/, 2204.622622/*POUND_UK*/, 1/*TONN*/, 0.984207/*UK_TONN*/, 1.102311/*US_TONN*/},
            /*UK_TONN*/
            {1016.046909/*KILOGRAM*/, 1016046.9088/*GRAM*/, 2240/*POUND_UK*/, 1.016047/*TONN*/, 1/*UK_TONN*/, 1.12/*US_TONN*/},
            /*US_TONN*/
            {907.18474/*KILOGRAM*/, 907184.74/*GRAM*/, 2000/*POUND_UK*/, 0.907185/*TONN*/, 1/*UK_TONN*/, 0.892857/*US_TONN*/}
    };

    private static <T> T convertUnits(Map<Units, Integer> units, double[][] unitsMatrix, Units from, Units to, T value) {
        if (value != null) {

            if (from.ordinal() == to.ordinal()) {
                return value;
            } else {
                Double v;
                if (value instanceof Double) {
                    v = (Double) value;
                } else if (value instanceof Float) {
                    v = ((Float) value).doubleValue();
                } else if (value instanceof Integer) {
                    v = ((Integer) value).doubleValue();
                } else if (value instanceof Long) {
                    v = ((Long) value).doubleValue();
                } else {
                    throw new RuntimeException("Unsupported type");
                }


                Integer indexFrom = units.get(from);
                Integer indexTo = units.get(to);
                if (indexFrom == null) {
                    throw new RuntimeException("Cannot convert from unit " + from.name());
                }
                if (indexTo == null) {
                    throw new RuntimeException("Cannot convert to unit " + to.name());
                }
                Double result = v * unitsMatrix[indexFrom][indexTo];
                if (value instanceof Double) {
                    return (T) result;
                } else if (value instanceof Float) {
                    return (T) Float.valueOf(result.floatValue());
                } else if (value instanceof Long) {
                    return (T) Long.valueOf(Math.round(result.doubleValue()));
                } else if (value instanceof Integer) {
                    return (T) Integer.valueOf(Math.round(result.floatValue()));
                } else {
                    return null;
                }
            }


        } else {
            return null;
        }
    }

    public static <T> T convert(LineUnits from, LineUnits to, T value) {
        return convertUnits(lineUnits, unitsLineMatrix, from, to, value);
    }

    public static <T> T convert(SpeedUnits from, SpeedUnits to, T value) {
        return convertUnits(speedUnits, unitsSpeedMatrix, from, to, value);
    }

    public static <T> T convert(TimeUnits from, TimeUnits to, T value) {
        return convertUnits(timeUnits, unitsTimeMatrix, from, to, value);
    }

    public static <T> T convert(WeightUnits from, WeightUnits to, T value) {
        return convertUnits(weightUnits, unitsWeightMatrix, from, to, value);
    }


}
